import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';

typedef IndexedWidgetListBuilder = List<Widget> Function(
    BuildContext context, int index);

class AnchorCategoryController extends ChangeNotifier {
  int selectedIndex = 0;

  void selectTo(int value) {
    selectedIndex = value;
    notifyListeners();
  }

  @override
  void dispose() {
    selectedIndex = 0;
    super.dispose();
  }
}

///
///自定义锚点分类列表
///
class AnchorCategory extends StatefulWidget {
  final double categoryWidth;
  final int itemCount;
  final IndexedWidgetBuilder categoryItemBuilder;
  final IndexedWidgetBuilder sectionItemBuilder;
  final IndexedWidgetListBuilder sectionOfChildrenBuilder;
  final bool sticky;
  final AnchorCategoryController? controller;

  const AnchorCategory(
      {super.key,
      required this.categoryItemBuilder,
      required this.sectionItemBuilder,
      required this.sectionOfChildrenBuilder,
      this.controller,
      double? categoryWidth,
      int? itemCount,
      bool? sticky})
      : categoryWidth = categoryWidth ?? 112,
        itemCount = itemCount ?? 0,
        sticky = sticky ?? true;

  @override
  State<AnchorCategory> createState() => _AnchorCategoryState();
}

class _AnchorCategoryState extends State<AnchorCategory> {
  final List<double> _sectionHeightList = [];
  final List<double> _childrenHeightList = [];
  final List<double> _scrollOffsetList = [];
  double _extraHeight = 0;

  final ScrollController _scrollController = ScrollController();
  int _selectedIndex = 0;
  bool _scrollLocked = false;

  @override
  void initState() {
    super.initState();
    if (widget.controller != null) {
      widget.controller!.addListener(_onIndexChange);
    }

    _scrollController.addListener(_onScrollChange);
  }

  void _onIndexChange() {
    if (_selectedIndex == widget.controller!.selectedIndex) {
      return;
    }

    _scrollLocked = true;

    _selectedIndex = widget.controller!.selectedIndex;
    widget.controller!.selectTo(_selectedIndex);
    _scrollController
        .animateTo(_scrollOffsetList[widget.controller!.selectedIndex],
            duration: const Duration(milliseconds: 300), curve: Curves.linear)
        .then((value) {
      _scrollLocked = false;
    });
  }

  void _onScrollChange() {
    if (_scrollLocked) {
      return;
    }

    double scrollOffset = _scrollController.offset;
    int selectedIndex = 0;
    for (int index = _scrollOffsetList.length - 1; index >= 0; index--) {
      selectedIndex = index;
      if (scrollOffset.roundToDouble() >= _scrollOffsetList[index]) {
        break;
      }
    }

    if (_selectedIndex != selectedIndex) {
      _selectedIndex = selectedIndex;
      widget.controller!.selectTo(selectedIndex);
    }
  }

  @override
  void dispose() {
    _scrollController.removeListener(_onScrollChange);
    _scrollController.dispose();

    if (widget.controller != null) {
      widget.controller!.removeListener(_onIndexChange);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.max,
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        SizedBox(
            width: widget.categoryWidth,
            child: LayoutBuilder(
              builder: (context, viewportConstraints) {
                return SingleChildScrollView(
                  child: ConstrainedBox(
                    constraints: BoxConstraints(
                        minHeight:
                            viewportConstraints.maxHeight != double.infinity
                                ? viewportConstraints.maxHeight
                                : 0),
                    child: Column(
                      mainAxisSize: MainAxisSize.max,
                      mainAxisAlignment: MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      children: List.generate(widget.itemCount, (index) {
                        return widget.categoryItemBuilder.call(context, index);
                      }),
                    ),
                  ),
                );
              },
            )),
        Expanded(
          child: AfterLayout(
            callback: (renderBox) {
              setState(() {
                for (int i = 0; i < widget.itemCount; i++) {
                  double scrollOffset = 0;
                  for (int j = 0; j < i; j++) {
                    scrollOffset +=
                        _sectionHeightList[j] + _childrenHeightList[j];
                  }
                  if (_scrollOffsetList.length > i) {
                    _scrollOffsetList[i] = scrollOffset;
                  } else {
                    _scrollOffsetList.add(scrollOffset);
                  }
                }

                if (widget.itemCount > 0) {
                  _extraHeight = max(
                      renderBox.size.height -
                          _childrenHeightList[widget.itemCount - 1],
                      0);
                } else {
                  _extraHeight = 0;
                }
              });
            },
            child: CustomScrollView(
              physics: const ClampingScrollPhysics(),
              controller: _scrollController,
              slivers: [
                ...(List<Widget>.generate(
                  widget.itemCount * 2,
                  (allIndex) {
                    int index = allIndex ~/ 2;
                    if (allIndex.isEven) {
                      //section
                      Widget sectionItem = AfterLayout(
                        callback: (renderBox) {
                          double height = renderBox.size.height;
                          setState(() {
                            if (_sectionHeightList.length > index) {
                              _sectionHeightList[index] = height;
                            } else {
                              _sectionHeightList.add(height);
                            }
                          });
                        },
                        child: widget.sectionItemBuilder.call(context, index),
                      );

                      if (widget.sticky) {
                        return StickySliverToBoxAdapter(
                          child: sectionItem,
                        );
                      } else {
                        return SliverToBoxAdapter(
                          child: sectionItem,
                        );
                      }
                    } else {
                      //children
                      return SliverToBoxAdapter(
                        child: AfterLayout(
                          callback: (renderBox) {
                            double height = renderBox.size.height;
                            setState(() {
                              if (_childrenHeightList.length > index) {
                                _childrenHeightList[index] = height;
                              } else {
                                _childrenHeightList.add(height);
                              }
                            });
                          },
                          child: Column(
                            children: widget.sectionOfChildrenBuilder
                                .call(context, index),
                          ),
                        ),
                      );
                    }
                  },
                )),
                SliverToBoxAdapter(
                  child: SizedBox(
                    height: _extraHeight,
                  ),
                )
              ],
            ),
          ),
        ),
      ],
    );
  }
}

typedef AfterLayoutCallback = Function(RenderBox ral);

class AfterLayout extends SingleChildRenderObjectWidget {
  final AfterLayoutCallback callback;

  const AfterLayout({
    super.key,
    required this.callback,
    super.child,
  });

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderAfterLayout(callback);
  }

  @override
  void updateRenderObject(context, RenderAfterLayout renderObject) {
    renderObject.callback = callback;
  }
}

class RenderAfterLayout extends RenderProxyBox {
  AfterLayoutCallback callback;

  RenderAfterLayout(this.callback);

  @override
  void performLayout() {
    super.performLayout();
    SchedulerBinding.instance
        .addPostFrameCallback((timeStamp) => callback(this));
  }
}

class StickySliverToBoxAdapter extends SingleChildRenderObjectWidget {
  const StickySliverToBoxAdapter({super.key, super.child});

  @override
  RenderObject createRenderObject(BuildContext context) =>
      _StickyRenderSliverToBoxAdapter();
}

class _StickyRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {
//查找前一个吸顶的section
  RenderSliver? _prev() {
    if (parent is RenderViewportBase) {
      RenderSliver? current = this;
      while (current != null) {
        current = (parent as RenderViewportBase).childBefore(current);
        if (current is _StickyRenderSliverToBoxAdapter &&
            current.geometry != null) {
          return current;
        }
      }
    }
    return null;
  }

  @override
  void performLayout() {
    if (child == null) {
      geometry = SliverGeometry.zero;
      return;
    }
    final SliverConstraints constraints = this.constraints;
    //摆放子View，并把constraints传递给子View
    child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
    //获取子View在滑动主轴方向的尺寸
    final double childExtent;
    switch (constraints.axis) {
      case Axis.horizontal:
        childExtent = child!.size.width;
      case Axis.vertical:
        childExtent = child!.size.height;
    }

    final double minExtent = childExtent;
    final double minAllowedExtent = constraints.remainingPaintExtent > minExtent
        ? minExtent
        : constraints.remainingPaintExtent;
    final double maxExtent = childExtent;
    final double paintExtent = maxExtent;
    final double clampedPaintExtent = clampDouble(
      paintExtent,
      minAllowedExtent,
      constraints.remainingPaintExtent,
    );
    final double layoutExtent = maxExtent - constraints.scrollOffset;

    geometry = SliverGeometry(
      scrollExtent: maxExtent,
      paintOrigin: min(constraints.overlap, 0.0),
      paintExtent: clampedPaintExtent,
      layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent),
      maxPaintExtent: maxExtent,
      maxScrollObstructionExtent: minExtent,
      hasVisualOverflow:
          true, // Conservatively say we do have overflow to avoid complexity.
    );

    //上推关键代码: 当前吸顶的Sliver被覆盖了多少，前一个吸顶的Sliver就移动多少
    RenderSliver? prev = _prev();
    if (prev != null && constraints.overlap > 0) {
      setChildParentData(
          _prev()!,
          constraints.copyWith(scrollOffset: constraints.overlap),
          _prev()!.geometry!);
    }
  }

  @override
  double childMainAxisPosition(covariant RenderBox child) => 0.0;
}
